Skip to content

Conversation

@panos-xyz
Copy link
Contributor

@panos-xyz panos-xyz commented Feb 9, 2026

Summary by CodeRabbit

  • New Features

    • L1 fee calculation for transactions.
    • Token fee information and Morph transaction fields (version, token id, rate, limit, memo) included in receipts.
  • Bug Fixes

    • Enforced block-level gas limits during execution.
  • Refactor

    • Rewrote block assembly and header construction for Morph flow.
    • Restructured block executor into a factory-based design.
    • Reworked receipt builder/API and simplified execution context handling.
  • Chores

    • Updated dependency declarations and simplified feature gating for primitives.

@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

Replaces Eth-based executor/assembler with Morph-specific implementations (executor factory, executor, assembler, receipt builder), adds L1/token fee handling into execution and receipts, updates EVM config/context APIs and Cargo feature/dependency declarations, and moves some primitives feature gating.

Changes

Cohort / File(s) Summary
Dependency & features
crates/evm/Cargo.toml, crates/primitives/Cargo.toml
Renamed/adjusted dependency entries and enabled/trimmed serde/serde-bincode-compat and reth-codec feature usage.
Block assembly
crates/evm/src/assemble.rs
Introduces MorphBlockAssembler owning chain_spec, builds standard Header then wraps into MorphHeader, and returns Morph-wrapped blocks (removed inner EthBlockAssembler delegation).
Executor factory
crates/evm/src/block/factory.rs
Adds MorphBlockExecutorFactory that wires MorphEvmFactory, MorphChainSpec, and DefaultMorphReceiptBuilder and implements BlockExecutorFactory::create_executor.
Block executor core
crates/evm/src/block/mod.rs
Replaces EthBlockExecutor with MorphBlockExecutor (owns MorphEvm, spec, receipt_builder); adds L1 fee calc, MorphTx field extraction, updated execute/commit/finish flows, and MorphReceipt collection.
Receipt builder
crates/evm/src/block/receipt.rs
Adds MorphReceiptBuilderCtx, MorphTxFields, MorphReceiptBuilder trait and DefaultMorphReceiptBuilder; receipts include L1 fees and optional MorphTx-specific fields.
EVM config & context
crates/evm/src/config.rs, crates/evm/src/lib.rs, crates/evm/src/context.rs
Switched context APIs to return EthBlockExecutionCtx (removed MorphBlockExecutionCtx), MorphEvmConfig now uses private MorphBlockExecutorFactory and exposes chain_spec()/evm_factory() via it.
Primitives impl
crates/primitives/src/lib.rs
Made reth_primitives_traits::NodePrimitives for MorphPrimitives unconditional and moved feature gating to the reth_ethereum_primitives import (reth-codec).

Sequence Diagram(s)

sequenceDiagram
    participant Config as MorphEvmConfig
    participant Factory as MorphBlockExecutorFactory
    participant EVM as MorphEvm
    participant DB as State/DB
    participant Receipt as DefaultMorphReceiptBuilder

    Config->>Factory: create_executor(evm, ctx)
    Factory->>EVM: construct MorphBlockExecutor(evm, spec, receipt_builder)
    EVM->>DB: execute transactions (apply, call inspectors)
    DB-->>EVM: execution results (gas_used, logs, state diffs)
    EVM->>Receipt: build_receipt(MorphReceiptBuilderCtx{tx, result, l1_fee, morph_fields})
    Receipt-->>EVM: MorphReceipt
    EVM-->>Factory: return receipts, gas_used
    Factory-->>Config: forward execution result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • anylots
  • chengwenxi

Poem

🐇 I stitched a factory, neat and bright,
Executors hum through the morphing night.
Receipts now carry L1's tiny fee,
Fields tucked in, hopping merrily,
Bugs beware — the rabbit's done it right.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main refactoring: migrating from direct block executor usage to a factory-based pattern, which is evident across multiple files (factory.rs addition, MorphBlockExecutorFactory introduction, config changes).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/receipt-builder

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
crates/evm/src/block/mod.rs (1)

299-308: Consider saturating_sub for defensive gas accounting.

If self.gas_used ever exceeds self.evm.block().gas_limit() due to an upstream bug, the subtraction on line 301 would panic (in debug) or wrap (in release). Using saturating_sub would make the guard check still catch the overrun gracefully (available gas = 0, all subsequent txns rejected).

Proposed fix
-        let block_available_gas = self.evm.block().gas_limit() - self.gas_used;
+        let block_available_gas = self.evm.block().gas_limit().saturating_sub(self.gas_used);

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@crates/evm/src/assemble.rs`:
- Around line 66-89: The Header construction in assemble_block currently sets
extra_data to Default::default(), dropping execution_ctx.extra_data; change the
header field initialization to use the extra data from the execution context
(e.g., replace extra_data: Default::default() with extra_data:
execution_ctx.extra_data.clone() or converted as needed) so the Header (inner)
preserves the block/payload extra_data; adjust any cloning or type conversion to
match the Header.extra_data type.

In `@crates/evm/src/block/mod.rs`:
- Around line 119-120: The code uses unwrap() on tx.fee_token_id() and
tx.fee_limit() which can panic; replace both unwrap() calls with fallible
unwrapping (e.g., tx.fee_token_id().ok_or(...)? and tx.fee_limit().ok_or(...)?),
returning a contextual error instead of panicking so the error propagates like
signer_unchecked does; include clear messages such as "missing fee_token_id for
morph tx" / "missing fee_limit for morph tx" when constructing the error so
callers can diagnose failures.
🧹 Nitpick comments (6)
crates/evm/Cargo.toml (1)

16-16: serde-bincode-compat feature on morph-primitives is always enabled, making the feature gate partially redundant.

Line 16 unconditionally enables morph-primitives/serde-bincode-compat. The [features] section at lines 41–43 also lists morph-primitives/serde-bincode-compat, but it has no additional effect since the feature is already always on. If the intent is to only enable it when downstream crates opt in, move it out of the default dependency features and keep it only in the [features] section. If it should always be on, remove it from the [features] section to avoid confusion.

Also applies to: 41-43

crates/evm/src/block/receipt.rs (1)

91-106: Consider logging or warning when token_fee_info is None for a MorphTx.

A MorphTx (0x7F) transaction is expected to carry token fee information. Silently falling back to with_l1_fee could mask upstream bugs where token fee info failed to be extracted. A tracing::warn! here would help surface such cases during debugging without breaking execution.

🔍 Proposed logging addition
             MorphTxType::Morph => {
                 // MorphTx transactions include token fee information
                 if let Some(token_info) = token_fee_info {
                     MorphReceipt::Morph(MorphTransactionReceipt::with_morph_tx(
                         inner,
                         l1_fee,
                         token_info.fee_token_id,
                         token_info.fee_rate,
                         token_info.token_scale,
                         token_info.fee_limit,
                     ))
                 } else {
-                    // Fallback: just include L1 fee if token info is missing
+                    // Fallback: just include L1 fee if token info is missing.
+                    // This is unexpected for MorphTx — log for diagnostics.
+                    tracing::warn!(target: "morph::receipt", "MorphTx missing token_fee_info, falling back to L1 fee only");
                     MorphReceipt::Morph(MorphTransactionReceipt::with_l1_fee(inner, l1_fee))
                 }
             }
crates/evm/src/block/factory.rs (1)

6-9: Minor: consolidate MorphEvmFactory import with the other crate-level imports.

use crate::evm::MorphEvmFactory at line 21 could be merged into the use crate::{...} block at lines 6–9.

♻️ Proposed consolidation
 use crate::{
     block::{DefaultMorphReceiptBuilder, MorphBlockExecutor},
-    evm::MorphEvm,
+    evm::{MorphEvm, MorphEvmFactory},
 };
 use alloy_evm::{
     Database,
@@ -19,8 +19,6 @@
 use morph_revm::evm::MorphContext;
 use std::sync::Arc;
 
-use crate::evm::MorphEvmFactory;
-

Also applies to: 21-21

crates/evm/src/lib.rs (1)

70-76: Consider encapsulating block_assembler behind an accessor for consistency.

executor_factory is private with an accessor at line 94, while block_assembler is a public field. For consistent encapsulation, consider making block_assembler private and adding a pub fn block_assembler(&self) accessor, similar to how chain_spec() and evm_factory() are exposed.

crates/evm/src/block/mod.rs (2)

43-56: PhantomData<I> is unnecessary since I is already constrained by the evm field.

The type parameter I appears in evm: MorphEvm<&'a mut State<DB>, I>, so PhantomData<I> adds no value. Removing it simplifies the struct.

Proposed fix
 pub(crate) struct MorphBlockExecutor<'a, DB: Database, I> {
     /// The EVM used by executor
     evm: MorphEvm<&'a mut State<DB>, I>,
     /// Chain specification
     spec: &'a MorphChainSpec,
     /// Receipt builder
     receipt_builder: &'a DefaultMorphReceiptBuilder,
     /// Receipts of executed transactions
     receipts: Vec<MorphReceipt>,
     /// Total gas used by executed transactions
     gas_used: u64,
-    /// Phantom data for inspector type
-    _phantom: PhantomData<I>,
 }

And update the constructor accordingly:

         Self {
             evm,
             spec,
             receipt_builder,
             receipts: Vec::new(),
             gas_used: 0,
-            _phantom: PhantomData,
         }

91-95: Duplicate hardfork determination logic.

The block-number/timestamp extraction and morph_hardfork_at call is duplicated between calculate_l1_fee (Lines 92–95) and get_token_fee_info (Lines 123–126). Consider extracting a small helper like fn current_hardfork(&self) -> MorphHardfork to reduce duplication.

Also applies to: 122-126

@panos-xyz panos-xyz changed the title refactor: refactor receipt builder build tx receipt when execute block refactor: migrate to factory-based block executor pattern Feb 9, 2026
@panos-xyz panos-xyz self-assigned this Feb 10, 2026
@panos-xyz panos-xyz added the enhancement New feature or request label Feb 10, 2026
@panos-xyz panos-xyz force-pushed the refactor/receipt-builder branch from 55bad64 to 4e7facd Compare February 11, 2026 08:48
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@crates/evm/src/assemble.rs`:
- Around line 119-125: Update the comment that wraps the block in MorphHeader to
accurately describe which L2-specific fields are populated later: clarify that
next_l1_msg_index is set later by the payload builder (see the logic around
builder::build/handle in the payload builder), whereas batch_hash remains
Default::default() (B256::ZERO) here and is populated later by the batch
submitter; reference the MorphHeader struct and its fields next_l1_msg_index and
batch_hash so reviewers can easily locate the affected fields.
🧹 Nitpick comments (6)
crates/evm/src/assemble.rs (1)

82-83: U256::to::<u64>() will silently truncate if number exceeds u64::MAX.

While block numbers won't realistically exceed u64::MAX, the timestamp conversion on line 103 has the same pattern. Both are safe in practice, but worth noting for consistency with the rest of the codebase.

crates/evm/src/block/receipt.rs (1)

175-193: Silent fallback for missing MorphTxFields on a MorphTx could mask upstream bugs.

When a transaction has type Morph (0x7F) but morph_tx_fields is None, the code silently falls back to with_l1_fee. This is defensive, but a MorphTx without its fields is unexpected and likely indicates a bug in get_morph_tx_fields. Consider logging a warning here to aid debugging.

Suggested improvement
             MorphTxType::Morph => {
                 // MorphTx transactions include MorphTx-specific fields
                 if let Some(fields) = morph_tx_fields {
                     MorphReceipt::Morph(MorphTransactionReceipt::with_morph_tx_v1(
                         inner,
                         l1_fee,
                         fields.version,
                         fields.fee_token_id,
                         fields.fee_rate,
                         fields.token_scale,
                         fields.fee_limit,
                         fields.reference,
                         fields.memo,
                     ))
                 } else {
-                    // Fallback: just include L1 fee if MorphTx fields are missing
+                    // Fallback: just include L1 fee if MorphTx fields are missing.
+                    // This should not happen in normal operation — log for debugging.
+                    tracing::warn!("MorphTx receipt built without MorphTxFields — falling back to L1-fee-only receipt");
                     MorphReceipt::Morph(MorphTransactionReceipt::with_l1_fee(inner, l1_fee))
                 }
             }
crates/evm/src/block/mod.rs (2)

63-76: PhantomData<I> is redundant — I already appears in the evm field type.

The type parameter I is used in evm: MorphEvm<&'a mut State<DB>, I>, so the compiler already knows how I relates to the struct. The _phantom: PhantomData<I> field is unnecessary.

Suggested cleanup
 pub(crate) struct MorphBlockExecutor<'a, DB: Database, I> {
     /// The EVM used by executor
     evm: MorphEvm<&'a mut State<DB>, I>,
     /// Chain specification
     spec: &'a MorphChainSpec,
     /// Receipt builder
     receipt_builder: &'a DefaultMorphReceiptBuilder,
     /// Receipts of executed transactions
     receipts: Vec<MorphReceipt>,
     /// Total gas used by executed transactions
     gas_used: u64,
-    /// Phantom data for inspector type
-    _phantom: PhantomData<I>,
 }

And update the constructor accordingly.


396-402: set_state_hook is a no-op — document if this is intentional or a TODO.

If state hooks are needed for tracing/debugging in the future, this silent no-op could cause confusion. Consider adding a TODO or FIXME comment if this is planned work.

crates/evm/src/lib.rs (2)

111-118: Inconsistent field visibility: executor_factory is private but block_assembler is public.

Consider making block_assembler private as well, since it's already accessible via self.block_assembler() through the ConfigureEvm trait (in config.rs line 27). Direct field access bypasses any future encapsulation.

Suggested change
 pub struct MorphEvmConfig {
     /// Internal block executor factory that creates `MorphBlockExecutor` instances.
     executor_factory: MorphBlockExecutorFactory,
 
     /// Block assembler for building `MorphHeader` blocks from execution results.
-    pub block_assembler: MorphBlockAssembler,
+    block_assembler: MorphBlockAssembler,
 }

179-237: Test coverage is present but limited to hardfork querying.

The test validates config construction and hardfork detection, which is good. Consider adding tests for create_executor and evm_factory delegation in a follow-up.

@panos-xyz panos-xyz merged commit 76eb20a into main Feb 12, 2026
9 checks passed
@panos-xyz panos-xyz deleted the refactor/receipt-builder branch February 12, 2026 06:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants